package classfile

type AttributeInfo interface {
	readInfo(reader *ClassReader)
}

//type AttributeInfo struct {
//	attributeNameIndex uint16
//	attributeLength    uint32
//	info               []uint8
//}

func readAttributes(reader *ClassReader, count uint16, constantPool ConstantPool) []AttributeInfo {
	attributes := make([]AttributeInfo, count)
	for i := range attributes {
		attributes[i] = readAttribute(reader, constantPool)
	}
	return attributes
}

func readAttribute(reader *ClassReader, constantPool ConstantPool) AttributeInfo {
	attributeNameIndex := reader.readUint16()
	//attributeLength := reader.readUint32()
	attrInfo := newAttributeInfo(attributeNameIndex, constantPool)
	attrInfo.readInfo(reader)
	return attrInfo
}

type UnparsedAttribute struct {
	attributeNameIndex uint16
	attributeLength    uint32
	info               []byte
}

func (self *UnparsedAttribute) readInfo(reader *ClassReader) {
	//self.attributeNameIndex = reader.readUint16()
	self.attributeLength = reader.readUint32()
	self.info = reader.readBytes(self.attributeLength)
}

type CodeAttribute struct {
	__constantPool__     ConstantPool
	attributeNameIndex   uint16
	attributeLength      uint32
	maxStack             uint16
	maxLocals            uint16
	codeLength           uint32
	code                 []byte
	exceptionTableLength uint16
	exceptionTable       []*ExceptionTableEntry
	attributesCount      uint16
	attributes           []AttributeInfo
}

type ExceptionTableEntry struct {
	startPc   uint16
	endPc     uint16
	handlerPc uint16
	catchType uint16
}

func (self *ExceptionTableEntry) StartPc() uint16 {
	return self.startPc
}

func (self *ExceptionTableEntry) EndPc() uint16 {
	return self.endPc
}

func (self *ExceptionTableEntry) HandlerPc() uint16 {
	return self.handlerPc
}

func (self *ExceptionTableEntry) CatchType() uint16 {
	return self.catchType
}

func (self *CodeAttribute) readInfo(reader *ClassReader) {
	//self.attributeNameIndex = reader.readUint16()
	self.attributeLength = reader.readUint32()
	self.maxStack = reader.readUint16()
	self.maxLocals = reader.readUint16()
	self.codeLength = reader.readUint32()
	self.code = reader.readBytes(self.codeLength)
	self.exceptionTableLength = reader.readUint16()
	self.exceptionTable = readExceptionTable(reader, self.exceptionTableLength)
	self.attributesCount = reader.readUint16()
	self.attributes = readAttributes(reader, self.attributesCount, self.__constantPool__)
}

func readExceptionTable(reader *ClassReader, exceptionTableLength uint16) []*ExceptionTableEntry {
	exceptionTable := make([]*ExceptionTableEntry, exceptionTableLength)
	for i := range exceptionTable {
		exceptionTable[i] = &ExceptionTableEntry{
			startPc:   reader.readUint16(),
			endPc:     reader.readUint16(),
			handlerPc: reader.readUint16(),
			catchType: reader.readUint16(),
		}
	}
	return exceptionTable
}

type ConstantValueAttribute struct {
	__constantPool__   ConstantPool
	attributeNameIndex uint16
	attributeLength    uint32
	constantValueIndex uint16
}

func (self *ConstantValueAttribute) ConstantValueIndex() uint16 {
	return self.constantValueIndex
}

func (self *ConstantValueAttribute) readInfo(reader *ClassReader) {
	//self.attributeNameIndex = reader.readUint16()
	self.attributeLength = reader.readUint32()
	self.constantValueIndex = reader.readUint16()
}

type DeprecatedAttribute struct {
	__constantPool__   ConstantPool
	attributeNameIndex uint16
	attributeLength    uint32
}

type SyntheticAttribute struct {
	__constantPool__   ConstantPool
	attributeNameIndex uint16
	attributeLength    uint32
}

type ExceptionsAttribute struct {
	__constantPool__    ConstantPool
	attributeNameIndex  uint16
	attributeLength     uint32
	numberOfExceptions  uint16
	exceptionIndexTable []uint16
}

func (self *ExceptionsAttribute) readInfo(reader *ClassReader) {
	//self.attributeNameIndex = reader.readUint16()
	self.attributeLength = reader.readUint32()
	self.numberOfExceptions = reader.readUint16()
	self.exceptionIndexTable = reader.readUint16s(self.numberOfExceptions)
}

func (self *ExceptionsAttribute) ExceptionIndexTable() []uint16 {
	return self.exceptionIndexTable
}

func (self *DeprecatedAttribute) readInfo(reader *ClassReader) {
	//self.attributeNameIndex = reader.readUint16()
	self.attributeLength = reader.readUint32()
}

func (self *SyntheticAttribute) readInfo(reader *ClassReader) {
	//self.attributeNameIndex = reader.readUint16()
	self.attributeLength = reader.readUint32()
}

type LineNumberTableAttribute struct {
	__constantPool__      ConstantPool
	attributeNameIndex    uint16
	attributeLength       uint32
	lineNumberTableLength uint16
	lineNumberTable       []*LineNumberTableEntry
}

func (self *LineNumberTableAttribute) readInfo(reader *ClassReader) {
	//self.attributeNameIndex = reader.readUint16()
	self.attributeLength = reader.readUint32()
	self.lineNumberTableLength = reader.readUint16()
	self.lineNumberTable = make([]*LineNumberTableEntry, self.lineNumberTableLength)
	for i := range self.lineNumberTable {
		self.lineNumberTable[i] = &LineNumberTableEntry{
			startPc:    reader.readUint16(),
			lineNumber: reader.readUint16(),
		}
	}
}

func (self *LineNumberTableAttribute) GetLineNumber(pc int) int {
	for i := len(self.lineNumberTable) - 1; i >= 0; i-- {
		entry := self.lineNumberTable[i]
		if pc >= int(entry.startPc) {
			return int(entry.lineNumber)
		}
	}
	return -1
}

type LineNumberTableEntry struct {
	startPc    uint16
	lineNumber uint16
}

type LocalVariableTableAttribute struct {
	__constantPool__         ConstantPool
	attributeNameIndex       uint16
	attributeLength          uint32
	localVariableTableLength uint16
	LocalVariableTable       []*LocalVariableTableEntry
}

func (self *LocalVariableTableAttribute) readInfo(reader *ClassReader) {
	//self.attributeNameIndex = reader.readUint16()
	self.attributeLength = reader.readUint32()
	self.localVariableTableLength = reader.readUint16()
	self.LocalVariableTable = make([]*LocalVariableTableEntry, self.localVariableTableLength)
	for i := range self.LocalVariableTable {
		self.LocalVariableTable[i] = &LocalVariableTableEntry{
			startPc:         reader.readUint16(),
			length:          reader.readUint16(),
			nameIndex:       reader.readUint16(),
			descriptorIndex: reader.readUint16(),
			index:           reader.readUint16(),
		}
	}

}

type LocalVariableTableEntry struct {
	startPc         uint16
	length          uint16
	nameIndex       uint16
	descriptorIndex uint16
	index           uint16
}

// u2 attribute_name_index;
//
//	u4 attribute_length;
//	u2 sourcefile_index;
type SourceFileAttribute struct {
	__constantPool__   ConstantPool
	attributeNameIndex uint16
	attributeLength    uint32
	sourceFileIndex    uint16
}

func (self *SourceFileAttribute) readInfo(reader *ClassReader) {
	self.attributeLength = reader.readUint32()
	self.sourceFileIndex = reader.readUint16()
}

func (self *SourceFileAttribute) FileName() string {
	return self.__constantPool__.getUtf8(self.sourceFileIndex)
}

type SignatureAttribute struct {
	__constantPool__   ConstantPool
	attributeNameIndex uint16
	attributeLength    uint32
	signatureIndex     uint16
}

func (self *SignatureAttribute) readInfo(reader *ClassReader) {
	self.attributeLength = reader.readUint32()
	self.signatureIndex = reader.readUint16()
}

func (self *SignatureAttribute) Signature() string {
	return self.__constantPool__.getUtf8(self.signatureIndex)
}

type LocalVariableTypeTableAttribute struct {
	__constantPool__             ConstantPool
	attributeNameIndex           uint16
	attributeLength              uint32
	localVariableTypeTableLength uint16
	localVariableTypeTable       []*localVariableTypeTableEntry
}

type localVariableTypeTableEntry struct {
	startPc        uint16
	length         uint16
	nameIndex      uint16
	signatureIndex uint16
	index          uint16
}

func (self *LocalVariableTypeTableAttribute) readInfo(reader *ClassReader) {
	self.attributeLength = reader.readUint32()
	self.localVariableTypeTableLength = reader.readUint16()
	self.localVariableTypeTable = make([]*localVariableTypeTableEntry, self.localVariableTypeTableLength)
	for i := range self.localVariableTypeTable {
		self.localVariableTypeTable[i] = &localVariableTypeTableEntry{
			startPc:        reader.readUint16(),
			length:         reader.readUint16(),
			nameIndex:      reader.readUint16(),
			signatureIndex: reader.readUint16(),
			index:          reader.readUint16(),
		}
	}
}

type InnerClassesAttribute struct {
	__constantPool__   ConstantPool
	attributeNameIndex uint16
	attributeLength    uint32
	numberOfClasses    uint16
	classes            []*InnerClassInfo
}

type InnerClassInfo struct {
	innerClassInfoIndex   uint16
	outerClassInfoIndex   uint16
	innerNameIndex        uint16
	innerClassAccessFlags uint16
}

func (self *InnerClassesAttribute) readInfo(reader *ClassReader) {
	self.attributeLength = reader.readUint32()
	self.numberOfClasses = reader.readUint16()
	self.classes = make([]*InnerClassInfo, self.numberOfClasses)
	for i := range self.classes {
		self.classes[i] = &InnerClassInfo{
			innerClassInfoIndex:   reader.readUint16(),
			outerClassInfoIndex:   reader.readUint16(),
			innerNameIndex:        reader.readUint16(),
			innerClassAccessFlags: reader.readUint16(),
		}
	}
}

type EnclosingMethodAttribute struct {
	__constantPool__   ConstantPool
	attributeNameIndex uint16
	attributeLength    uint32
	classIndex         uint16
	methodIndex        uint16
}

func (self *EnclosingMethodAttribute) readInfo(reader *ClassReader) {
	self.attributeLength = reader.readUint32()
	self.classIndex = reader.readUint16()
	self.methodIndex = reader.readUint16()
}

type BootstrapMethodsAttribute struct {
	__constantPool__    ConstantPool
	attributeNameIndex  uint16
	attributeLength     uint32
	numBootstrapMethods uint16
	bootstrapMethods    []*BootstrapMethod
}

type BootstrapMethod struct {
	bootstrapMethodRef    uint16
	numBootstrapArguments uint16
	bootstrapArguments    []uint16
}

func (self *BootstrapMethodsAttribute) readInfo(reader *ClassReader) {
	self.attributeLength = reader.readUint32()
	self.numBootstrapMethods = reader.readUint16()
	self.bootstrapMethods = make([]*BootstrapMethod, self.numBootstrapMethods)
	for i := range self.bootstrapMethods {
		__bootstrapMethodRef__ := reader.readUint16()
		__numBootstrapArguments__ := reader.readUint16()
		self.bootstrapMethods[i] = &BootstrapMethod{
			bootstrapMethodRef:    __bootstrapMethodRef__,
			numBootstrapArguments: __numBootstrapArguments__,
			bootstrapArguments:    reader.readUint16s(__numBootstrapArguments__),
		}
	}

}

func newAttributeInfo(attributeNameIndex uint16, cp ConstantPool) AttributeInfo {
	attrName := cp.getUtf8(attributeNameIndex)
	switch attrName {
	case "Code":
		return &CodeAttribute{__constantPool__: cp, attributeNameIndex: attributeNameIndex}
	case "ConstantValue":
		return &ConstantValueAttribute{__constantPool__: cp, attributeNameIndex: attributeNameIndex}
	case "Deprecated":
		return &DeprecatedAttribute{__constantPool__: cp, attributeNameIndex: attributeNameIndex}
	case "Exceptions":
		return &ExceptionsAttribute{__constantPool__: cp, attributeNameIndex: attributeNameIndex}
	case "LineNumberTable":
		return &LineNumberTableAttribute{__constantPool__: cp, attributeNameIndex: attributeNameIndex}
	case "LocalVariableTable":
		return &LocalVariableTableAttribute{__constantPool__: cp, attributeNameIndex: attributeNameIndex}
	case "SourceFile":
		return &SourceFileAttribute{__constantPool__: cp, attributeNameIndex: attributeNameIndex}
	case "Synthetic":
		return &SyntheticAttribute{__constantPool__: cp, attributeNameIndex: attributeNameIndex}
	case "Signature":
		return &SignatureAttribute{__constantPool__: cp, attributeNameIndex: attributeNameIndex}
	case "LocalVariableTypeTable":
		return &LocalVariableTypeTableAttribute{__constantPool__: cp, attributeNameIndex: attributeNameIndex}
	case "InnerClasses":
		return &InnerClassesAttribute{__constantPool__: cp, attributeNameIndex: attributeNameIndex}
	case "EnclosingMethod":
		return &EnclosingMethodAttribute{__constantPool__: cp, attributeNameIndex: attributeNameIndex}
	case "BootstrapMethods":
		return &BootstrapMethodsAttribute{__constantPool__: cp, attributeNameIndex: attributeNameIndex}
	default:
		return &UnparsedAttribute{attributeNameIndex: attributeNameIndex}
	}
}

func (self *CodeAttribute) MaxLocals() uint16 {
	return self.maxLocals
}

func (self *CodeAttribute) MaxStack() uint16 {
	return self.maxStack
}

func (self *CodeAttribute) Code() []byte {
	return self.code
}

func (self *CodeAttribute) ExceptionTable() []*ExceptionTableEntry {
	return self.exceptionTable
}

func (self *CodeAttribute) LineNumberTableAttribute() *LineNumberTableAttribute {
	attrs := self.attributes
	for _, attr := range attrs {
		switch attr.(type) {
		case *LineNumberTableAttribute:
			return attr.(*LineNumberTableAttribute)
		}
	}
	return nil
}
